Spring Bean 的杂项知识
Bean 的作用域有哪些?
Scope | Description |
---|---|
singleton | 单例模式 (默认就是单例模式) |
prototype | 原型模式 每次从容器中 get 的时候,都会产生一个新对象 |
request | 将单个 Bean 定义限定为单个 HTTP 请求的生命周期。 |
session | 将单个 Bean 定义限定为 HTTP 会话的生命周期。 |
application | 将单个Bean 定义限定为 ServletContext 的生命周期。 |
websocket | 将单个 Bean 定义限定为 WebSocket 的生命周期。 |
request、session、application、websocket 仅在支持 web 的 Spring 应用程序上下文中有效。
<bean id="student2" class="com.alsritter.pojo.Student" p:name="张三" scope="prototype"/>
因为使用了原型模式,所以每取得一次就创建一个
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student2");
Student student2 = (Student) context.getBean("student2");
System.out.println(student == student2);
}
-->
false
Bean 单例的线程安全
当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题,而容器本身并没有提供 Bean 的线程安全策略,因此可以说 Spring 容器中的 Bean 本身不具备线程安全的特性,但是具体还是要结合具体 scope 的 Bean 去研究
先从单例与原型 Bean 分别进行说明
原型 Bean
对于原型 Bean,每次创建一个新对象,也就是线程之间并不存在 Bean 共享,自然是不会有线程安全的问题。
单例 Bean
对于单例 Bean,所有线程都共享一个单例实例 Bean,因此是存在资源的竞争。如果单例 Bean,是一个无状态 Bean,也就是线程中的操作不会对 Bean 的成员执行查询以外的操作,那么这个单例 Bean 是线程安全的。比如 Spring mvc 的 Controller、Service、Dao 等,这些 Bean 大多是无状态的,只关注于方法本身。
下面是一个有状态的 Bean 示例:(经典的并发问题)
public class TestManagerImpl implements TestManager {
private User user;
public void deleteUser(User e) throws Exception {
user = e; //1
prepareData(e);
}
public void prepareData(User e) throws Exception {
user = getUserByID(e.getId()); //2
//使用user.getId(); //3
}
}
如果该 Bean 配置为 singleton,会出现什么样的状况呢? 如果有2个用户访问,都调用到了该 Bean,假定为 user1、user2。
当 user1 调用到程序中的步骤1 的时候,该 Bean 的私有变量 user 被赋值为 user1,当 user1 的程序走到步骤2 的时候,该 Bean 的私有变量 user 被重新赋值为 user1_create,理想的状况,当 user1 走到 3步骤的时候,私有变量 user 应该为user1_create ;但如果在 user1 调用到 3步骤之前,user2 开始运行到了步骤1了,由于单态的资源共享,则私有变量 user 被修改为 user2;这种情况下,user1 的步骤3 用到的 user.getId()
实际用到是 user2 的对象。
如果需要在 Bean 里保存状态常见的有 2 种解决办法:
- 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
- 改变 Bean 的作用域为 “prototype”:每次请求都会创建一个新的 bean 实例,自然不会存在线程安全问题。
@Component 和 @Bean 的区别
作用对象不同: @Component
注解作用于类,而 @Bean
注解作用于方法。
Spring 帮助我们管理 Bean 分为两个部分,一个是注册 Bean,一个装配 Bean。
完成这两个动作有三种方式,一种是使用自动配置的方式、一种是使用 JavaConfig 的方式,一种就是使用 XML 配置的方式。(注意,虽然后面开始使用注解了,但是早在 XML 时期就这么分了)
@Compent
作用就相当于 XML配置,@Component
通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(可以使用 @ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)
@Component
public class Student {
private String name = "lkm";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Bean
需要在配置类中使用,即类上需要加上 @Configuration
注解
@Configuration
public class WebSocketConfig {
@Bean
public Student student(){
return new Student();
}
}
相当于
<beans>
<bean id="student" class="com.temp.Student"/>
</beans>
两者都可以通过 @Autowired
装配
@Autowired
Student student;
那为什么有了
@Compent
,还需要@Bean
呢?
1、如果想要将 第三方库中的组件 装配到应用中,在这种情况下,是没有办法在它的类上添加 @Component
注解的,因此就不能使用自动化装配的方案了,但是这时可以使用 @Bean
,当然也可以使用 XML 配置。
2、使用 @Bean
进行装配更加的灵活,如下代码
// 这个 status 会根据类型自动注入
@Bean
public OneService getService(status) {
switch (status) {
case 1:
return new serviceImpl1();
case 2:
return new serviceImpl2();
case 3:
return new serviceImpl3();
}
}
注册为 Bean 的注解
一般使用 @Autowired
注解自动装配 bean,要想把类标识成可用于 @Autowired
注解自动装配的 bean 的类,采用以下注解可实现:
@Component
:通用的注解,可标注任意类为 Spring 组件。如果一个 Bean不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。
Bean 的一生
Spring Bean 的生命周期只有下面这四个阶段
- 实例化 Instantiation:包装对象,执行构造方法,用于提前暴露实例解决循环依赖问题
- 属性赋值 Populate:寻找并且注入依赖,依赖的 Bean 还会递归调用 getBean 方法获取)
- 初始化 Initialization:调用自定义的初始化方法(不是构造方法,而是调用
<bean>
的init-method
属性指定的初始化方法)) - 销毁 Destruction
实例化 -> 属性赋值 -> 初始化 -> 销毁
实例化和属性赋值对应构造方法和 setter 方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。
Bean 的创建主要逻辑都在 doCreate()
方法中,逻辑很清晰,就是顺序调用以下三个方法,这三个方法与三个生命周期阶段一一对应
- createBeanInstance() -> 实例化
- populateBean() -> 属性赋值
- initializeBean() -> 初始化
这个 doCreateBean 位于 org.springframework.beans.factory.support
包下
// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 实例化阶段!
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性赋值阶段!
populateBean(beanName, mbd, instanceWrapper);
// 初始化阶段!
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
至于销毁,是在容器关闭时调用的,详见 ConfigurableApplicationContext#close()
注意:使用 final
修饰形参分两种情况,修饰基本类型时是为了让这个参数只读,修饰引用类型时是为了防止引用的地址被改变,否则会无法通过编译
Reference
参考资料 请别再问Spring Bean的生命周期了! 参考资料 javaguide Spring bean 参考资料 5.3 @Component 和 @Bean 的区别是什么? 参考资料 @Component 和 @Bean 的区别 参考资料 Spring 中的bean 是线程安全的吗?